﻿/*======================================================================
== Copyright : BlueCurve (c)
== Licence   : Gnu/GPL v2.x
== Author    : Teddy Albina
== Email     : bluecurveteam@gmail.com
== Web site  : http://www.codeplex.com/BlueCurve
========================================================================*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using BlueCurve.Common;
using BlueCurve.EnumAction;
using BlueCurve.IndexationContract;
using ICSharpCode.SharpZipLib.Zip;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;


namespace QueryServer
{
    /// <summary>
    /// Class contenant les méthodes d'indexation
    /// </summary>
    public static class LuceneIndexWriter
    {
        #region 'Configuration'

        /// <summary>
        /// Chemin d'accès de l'index
        /// </summary>
        private static string s_Indexlocation { get; set; }
        /// <summary>
        /// Pile des données à indexer
        /// </summary>
        private static Queue s_IndexBuffer = new Queue();
        /// <summary>
        /// Objet lecture de l'index
        /// </summary>
        private static IndexReader s_Indexreader { get; set; }
        /// <summary>
        /// Objet écriture de l'index
        /// </summary>
        private static IndexWriter s_Indexwriter { get; set; }
        /// <summary>
        /// Index à attacher à l'index principal
        /// </summary>
        private static IndexReader[] s_AttachIndex { get; set; }
        /// <summary>
        /// Permets de savoir si on doit ou non utiliser de multiple index
        /// </summary>
        public static bool s_UseMultiIndexes { get; set; }
        /// <summary>
        /// Fonctions communes
        /// </summary>
        private static CommonFunctions s_Common = new CommonFunctions();
        /// <summary>
        /// Permets de savoir si le serveur est en mode de sauvegarde
        /// </summary>
        private static bool s_IsBackup { get; set; }
        /// <summary>
        /// Permet de laisser tourner le thread d'indexation
        /// </summary>
        private static bool s_IsRun = true;
        /// <summary>
        /// Nombre de threads en cours
        /// </summary>
        private static int s_AliveThreads = 1;
        /// <summary>
        /// Evenement de sortie du mode de sauvegarde
        /// </summary>
        public static event EventHandler EndOfBackupMode;

        /// <summary>
        /// Permet de stopper le thread
        /// </summary>
        public static bool IsStop
        {
            set { s_IsRun = false; }
        }

        /// <summary>
        /// Retourne le nombre de thread en cours
        /// </summary>
        public static int AliveThreads
        {
            get { return s_AliveThreads; }
        }
       

        #endregion

        




        #region 'Functions'

        /// <summary>
        /// Ajoute des données à indexés dans la pile
        /// </summary>
        /// <param name="indexdata">Données à ajouter dans la pile</param>
        public static bool Add(BlueCurve.IndexationContract.IndexationData indexdata)
        {
            lock (s_IndexBuffer.SyncRoot)
            {
                Console.WriteLine("Ajout de données dans la pile d'indexation");
                s_IndexBuffer.Enqueue(indexdata);
            }
            return true;
        }


        /// <summary>
        /// Obtient un élément de la pile
        /// </summary>
        /// <returns>IndexationContract.IndexationData</returns>
        private static BlueCurve.IndexationContract.IndexationData Get()
        {
            lock (s_IndexBuffer.SyncRoot)
            {
                if (s_IndexBuffer.Count > 0)
                    return (BlueCurve.IndexationContract.IndexationData)s_IndexBuffer.Dequeue();
            }
            return null;
        }


        /// <summary>
        /// Initialise un index
        /// </summary>
        /// <param name="ilocation">Chemin d'accès de l'index</param>
        private static void InitIndex(string ilocation)
        {
            if (!Directory.Exists(ilocation))
            {
                Directory.CreateDirectory(ilocation);
                s_Indexwriter = new IndexWriter(ilocation, new StandardAnalyzer(), true);
            }
            else
            {
                try
                {
                    s_Indexwriter = new IndexWriter(ilocation, new StandardAnalyzer(), false);
                }
                catch
                {
                    s_Indexwriter = new IndexWriter(ilocation, new StandardAnalyzer(), true);
                }
            }
            s_Indexwriter.Close();
        }

        
        /// <summary>
        /// Initialise l'index
        /// </summary>
        public static void Configure(string ilocation)
        {
            // Initialise la liaison à l'évenement backup
            Tcpservice.EnterBackupMode += new EventHandler(Tcpservice_EnterBackupMode);

            s_IsBackup = false;
            s_IsRun = true;
            List<IndexReader> Xindexes = new List<IndexReader>();

            if (s_UseMultiIndexes)
            {
                string system = string.Concat(System.Environment.SystemDirectory.Split(new char[] { '\\' })[0], '\\');
                DriveInfo[] dInfos = DriveInfo.GetDrives();

                for (int i = 0; i < dInfos.Length; i++)
                {
                    if (dInfos[i].DriveType == DriveType.Fixed && !dInfos[i].Name.Equals(system, StringComparison.CurrentCultureIgnoreCase))
                    {
                        if (!Directory.Exists(string.Concat(dInfos[i].Name, @"\Index")) ||
                             Directory.GetFileSystemEntries(string.Concat(dInfos[i].Name, @"\Index")).Length == 0)
                        {
                            try
                            {
                                Directory.CreateDirectory(string.Concat(dInfos[i].Name, @"\Index"));
                            }
                            catch { }
                            InitIndex(string.Concat(dInfos[i].Name, @"\Index"));
                            Xindexes.Add(IndexReader.Open(string.Concat(dInfos[i].Name, @"\Index")));
                        }
                    }
                }
            }

            InitIndex(ilocation);
            s_Indexlocation = ilocation;

            if (Xindexes.Count > 0 && s_UseMultiIndexes)
                s_AttachIndex = Xindexes.ToArray();
            else
                s_AttachIndex = new IndexReader[] { };
            
            s_Indexwriter.Close();
        }


        /// <summary>
        /// Sauvegarde de la pile d'indexation
        /// </summary>
        private static void SaveQueue()
        {
            lock (s_IndexBuffer.SyncRoot)
            {
                if (s_IndexBuffer.Count < 1)
                    return;

                using (Stream stream = File.Open("IndexQueue.bin", FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    formatter.Serialize(stream, s_IndexBuffer);
                    stream.Close();
                }
            }
        }


        /// <summary>
        /// Restauration de la pile d'indexation
        /// </summary>
        private static void ReloadQueue()
        {
            if (File.Exists("IndexQueue.bin"))
            {
                try
                {
                    using (Stream stream = File.Open("IndexQueue.bin", FileMode.Open, FileAccess.Read, FileShare.None))
                    {
                        BinaryFormatter formatter = new BinaryFormatter();
                        s_IndexBuffer = (Queue)formatter.Deserialize(stream);
                        stream.Close();
                    }
                }
                catch
                {
                }
                File.Delete("IndexQueue.bin");
            }
        }


        /// <summary>
        /// Fonction principal de la bibliothèque
        /// </summary>
        public static void RunIndexation()
        {
            if (File.Exists("IndexQueue.bin"))
                ReloadQueue();

            while (s_IsRun)
            {
                if (!s_IsBackup)
                {
                    BlueCurve.IndexationContract.IndexationData indexdata = Get();

                    if (indexdata != null)
                    {
                        switch (indexdata.Action)
                        {
                            case IndexAction.INDEX:
                                IndexDocument(indexdata);
                                break;
                            case IndexAction.UPDATE:
                                UpdateDocument(indexdata);
                                break;
                            case IndexAction.DELETE:
                                DeleteDocument(indexdata);
                                break;
                            default:
                                break;
                        }
                    }
                }
                else
                {
                    StartBackup();
                }
                Thread.Sleep(2000);
            }

            SaveQueue();

            s_AliveThreads = 0;
        }

        #endregion







        #region 'Lucene'

        /// <summary>
        /// Index un document
        /// </summary>
        /// <param name="indexdata">Données à indexer</param>
        private static void IndexDocument(BlueCurve.IndexationContract.IndexationData indexdata)
        {
            s_Indexwriter = new IndexWriter(s_Indexlocation, new StandardAnalyzer(), false);
            if (s_AttachIndex.Length > 0 && s_UseMultiIndexes)
                s_Indexwriter.AddIndexes(s_AttachIndex);

            Document doc = new Document();
            doc.Add(new Field("Url", indexdata.Path, Field.Store.YES, Field.Index.UN_TOKENIZED, Field.TermVector.NO));
            doc.Add(new Field("Title", indexdata.Title, Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.WITH_POSITIONS));
            doc.Add(new Field("Content", indexdata.Text, Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
            doc.Add(new Field("Description", indexdata.Description, Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.WITH_POSITIONS));
            doc.Add(new Field("Size", indexdata.Size.ToString(), Field.Store.YES, Field.Index.NO));
            doc.Add(new Field("Language", indexdata.Language, Field.Store.YES, Field.Index.UN_TOKENIZED));
            doc.Add(new Field("LastModified", indexdata.LastModified.ToString(), Field.Store.YES, Field.Index.NO));
            doc.Add(new Field("ContentType", indexdata.ContentType, Field.Store.YES, Field.Index.UN_TOKENIZED, Field.TermVector.NO));
            doc.Add(new Field("IsDesktop", indexdata.IsDesktop.ToString(), Field.Store.YES, Field.Index.UN_TOKENIZED, Field.TermVector.NO));
            doc.Add(new Field("Action", indexdata.Action.ToString(), Field.Store.YES, Field.Index.NO, Field.TermVector.NO));
            doc.Add(new Field("KeyWords", indexdata.KeyWords, Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.WITH_OFFSETS));
            doc.Add(new Field("RevisitAfter", Convert.ToString(indexdata.RevisitAfter), Field.Store.YES, Field.Index.NO));
            doc.Add(new Field("Domain", s_Common.GetDomainFromLink(indexdata.Path), Field.Store.YES, Field.Index.UN_TOKENIZED));
            doc.Add(new Field("Date", DateTime.Now.ToString(), Field.Store.YES, Field.Index.UN_TOKENIZED));
            doc.Add(new Field("Year", DateTime.Now.Year.ToString(), Field.Store.YES, Field.Index.UN_TOKENIZED));
            doc.Add(new Field("Month", DateTime.Now.Month.ToString(), Field.Store.YES, Field.Index.UN_TOKENIZED));
            //doc.Add(new Field("Type", indexdata.E, Field.Store.YES, Field.Index.UN_TOKENIZED, Field.TermVector.NO));

            //doc.Add(new Field("", "1", Field.Store.YES, Field.Index.UN_TOKENIZED));
            //doc.Add(


            if (!string.IsNullOrEmpty(indexdata.Thumbnail))
            {
                doc.Add(new Field("Thumbnail", indexdata.Thumbnail, Field.Store.NO, Field.Index.NO));
            }

            if (indexdata.RssFeeds.Count > 0)
            {
                indexdata.RssFeeds.ForEach(
                    delegate(string rss)
                    {
                        doc.Add(new Field("Rss", rss, Field.Store.YES, Field.Index.NO));
                    });
            }

            if (indexdata.MetaDatas.Count > 0)
            {
                foreach (KeyValuePair<string, Meta> pair in indexdata.MetaDatas)
                {
                    switch (pair.Value.TokenValue)
                    {
                        case Tokenize.YES:
                            doc.Add(new Field(pair.Key, Convert.ToString(pair.Key), Field.Store.YES, Field.Index.TOKENIZED));
                            break;
                        case Tokenize.NO:
                            doc.Add(new Field(pair.Key, Convert.ToString(pair.Key), Field.Store.YES, Field.Index.UN_TOKENIZED));
                            break;
                        default:
                            break;
                    }
                }
            }

            if (indexdata.LinksAndAnchor.Count > 0)
            {
                foreach (KeyValuePair<string, string> pair in indexdata.LinksAndAnchor)
                {
                    doc.Add(new Field("Anchor", pair.Value, Field.Store.YES, Field.Index.NO));
                }
            }

            // desktop
            if (indexdata.IsDesktop)
            {
                doc.Add(new Field("CreationDate", indexdata.CreationDate.ToString(), Field.Store.YES, Field.Index.UN_TOKENIZED));
                doc.Add(new Field("LastAccess", indexdata.LastAccess.ToString(), Field.Store.YES, Field.Index.UN_TOKENIZED));   
            }

            s_Indexwriter.AddDocument(doc, new StandardAnalyzer());
            s_Indexwriter.Optimize();
            s_Indexwriter.Close();
            s_Indexwriter = null;
        }

        /// <summary>
        /// Mets à jour un document
        /// </summary>
        /// <param name="indexdata">Données à mettre à jour</param>
        private static void UpdateDocument(BlueCurve.IndexationContract.IndexationData indexdata)
        {
            DeleteDocument(indexdata);
            IndexDocument(indexdata);
        }

        /// <summary>
        /// Supprime un document
        /// </summary>
        /// <param name="indexdata">Données à supprimer</param>
        private static void DeleteDocument(BlueCurve.IndexationContract.IndexationData indexdata)
        {
            lock (s_Indexreader)
            {
                Term term = null;
                s_Indexreader = IndexReader.Open("Index");

                if (!string.IsNullOrEmpty(indexdata.OldPath))
                    term = new Term("Url", indexdata.OldPath);
                else
                    term = new Term("Url", indexdata.Path);

                s_Indexreader.DeleteDocuments(term);
                s_Indexreader.Close();
            }
        }

        /// <summary>
        /// Permets de savoir combien de document ont été indexé
        /// </summary>
        /// <returns></returns>
        public static int CountDocument()
        {
            lock (s_Indexreader)
            {
                try
                {
                    s_Indexreader = IndexReader.Open("Index");
                    int docs = s_Indexreader.NumDocs();
                    s_Indexreader.Close();
                    return docs;
                }
                catch
                {
                    return -1;
                }
            }
        }

        #endregion







        #region 'Backup'

        /// <summary>
        /// Démarre la sauvegarde de la base de données
        /// </summary>
        private static void StartBackup()
        {
            Save(s_Indexlocation, "Save");
            s_IsBackup = false;
            EndOfBackupMode("", EventArgs.Empty);
        }


        /// <summary>
        /// Permets de sauvegarder les indexes
        /// </summary>
        /// <param name="indexlocation">Emplacement de l'index</param>
        /// <param name="indexdestination">Emplacement de la copie de l'index</param>
        private static void Save(string indexlocation, string indexdestination)
        {
            DirectoryInfo info = new DirectoryInfo(indexlocation);

            FileInfo[] files = info.GetFiles();

            using (FileStream stream = File.Create(string.Concat(indexdestination, Guid.NewGuid().ToString(), ".zip"), 1024, FileOptions.None))
            {
                ZipOutputStream zipOstrem = new ZipOutputStream(stream);
                zipOstrem.SetLevel(9);

                foreach(FileInfo file in files)
                {
                    using (FileStream fs = File.OpenRead(string.Concat(info.FullName, '\\' , file.Name)))
                    {
                        byte[] buffer = new byte[fs.Length];
                        fs.Read(buffer, 0, buffer.Length);

                        ZipEntry entry = new ZipEntry(file.Name);
                        zipOstrem.PutNextEntry(entry);
                        zipOstrem.Write(buffer, 0, buffer.Length);
                        fs.Close();
                    }                  
                }
                zipOstrem.Finish();
                zipOstrem.Close();
                stream.Close();
            }
        }


        /// <summary>
        /// Permet de restaurer les index
        /// </summary>
        /// <param name="indexlocation">Localisation de la sauvegarde</param>
        /// <param name="indexdestination">Emplacement de l'index</param>
        private static void Restore(string indexlocation, string indexdestination)
        {
            using (ZipInputStream zipIStream = new ZipInputStream(File.OpenRead(indexlocation)))
            {
                ZipEntry theEntry;

                while ((theEntry = zipIStream.GetNextEntry()) != null)
                {
                    if (theEntry.IsDirectory)
                    {
                        Directory.CreateDirectory(string.Concat(indexdestination, '\\', theEntry.Name));
                    }
                    else
                    {
                        int size = 2048;
                        byte[] data = new byte[size];
                        using (FileStream fs = new FileStream(string.Concat(indexdestination, '\\', theEntry.Name), FileMode.Create))
                        {
                            while ((size = zipIStream.Read(data, 0, data.Length)) > 0)
                            {
                                fs.Write(data, 0, size);
                            }
                            fs.Flush();
                            fs.Close();
                        }
                    }
                }
                zipIStream.Close();
            }
        }

        #endregion







        #region 'Event'

        /// <summary>
        /// Reçoie l'ordre de passer en mode sauvegarde
        /// </summary>
        private static void Tcpservice_EnterBackupMode(object sender, EventArgs e)
        {
            s_IsBackup = true;
        }

        #endregion
    }
}
